﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading;
using System.IO;
using System.Text.RegularExpressions;
using XBMC.Cache.Manager.Shared;

namespace XBMC.Cache.Manager
{
    [System.ComponentModel.DesignerCategory("")]
    public partial class XBMCCacheManager : ServiceBase
    {
        private Timer m_Timer;
        private bool m_LogOnly;
        private string m_logMessage = string.Empty;

        public XBMCCacheManager()
        {
            InitializeComponent();

            m_Timer = new Timer(new TimerCallback(EventTimerCallback), null,
                Timeout.Infinite, Timeout.Infinite);
        }

        #region Service Events

        protected override void OnStart(string[] args)
        {
            if (!StartTimer()) base.Stop();
        }

        protected override void OnStop()
        {
            m_Timer = null;

#if LOGEVENTS
            EventLogHelper.WriteEvent("Stopping service.", EventLogEntryType.Information);
#endif

            // Indicate a successful exit.
            this.ExitCode = 0;
        }

        #endregion

        #region Video - Cleaning

        private void CleanVideoDatabase()
        {
            // Load the database records
            DatabaseHelper data = new DatabaseHelper(true);
            string sql = data.LoadSQLStatement("SQL.VideoSelectFiles.sql");
            List<DBObjectPath> dbFiles = data.Read<DBObjectPath>(sql);

            string logMessage = string.Empty;

            foreach (DBObjectPath dbFile in dbFiles)
            {
                if (dbFile.PhysicalFolder != null)
                {
                    if (!File.Exists(dbFile.PhysicalFile))
                    {
                        logMessage += "FileID=" + dbFile.FileID.ToString() + ", " + dbFile.PhysicalFile + "\r\n";

                        if (!m_LogOnly) DeleteVideoFromDatabase(dbFile);
                    }
                }
            }

            if (!m_LogOnly) PurgeVideoLinkTables();

            if (logMessage != string.Empty)
            {
                if (m_LogOnly)
                    logMessage = "The following files would be deleted from the database:\r\n\r\n" + logMessage;
                else
                    logMessage = "The following files have been deleted from the database:\r\n\r\n" + logMessage;

                EventLogHelper.WriteEvent(logMessage, EventLogEntryType.Information);
            }
        }

        private void DeleteVideoFromDatabase(DBObjectPath dbFile)
        {
            DatabaseHelper data = new DatabaseHelper(true);
            string sql = data.LoadSQLStatement("SQL.VideoDeleteFiles.sql");
            sql = String.Format(sql, dbFile.FileID);

            data.Execute(sql);
        }

        private void PurgeVideoLinkTables()
        {
            DatabaseHelper data = new DatabaseHelper(true);
            string sql = data.LoadSQLStatement("SQL.VideoPurgeLinkedTables.sql");
            data.Execute(sql);
        }

        private void CleanVideoThumbnailCache()
        {
            // Load the database records
            DatabaseHelper data = new DatabaseHelper(true);
            string sql = data.LoadSQLStatement("SQL.VideoSelectThumbnailCache.sql");
            List<DBCacheFile> databaseFiles = data.Read<DBCacheFile>(sql);

            string root = Path.Combine(RegistryHelper.LoadRegistryOption("LocalCacheFolder"), "Video");
            m_logMessage = string.Empty;
            int totalToDelete = 0;

            string[] fileList = GetFiles(root, 
                new string[] { ".tbn", ".jpg", ".dds" }, 
                new string[] { @"*\Bookmarks\*" }, SearchOption.AllDirectories);

            foreach (string file in fileList)
            {
                if (!databaseFiles.Exists(f => f.CacheHashFile == Path.GetFileNameWithoutExtension(file)))
                {
                    totalToDelete++;

                    if (!m_LogOnly)
                        DeleteCacheFile(file);
                    else
                        m_logMessage += file + "\r\n";
                }
            }

            if (m_logMessage != string.Empty)
            {
                if (m_LogOnly)
                {
                    if (Convert.ToBoolean(RegistryHelper.LoadRegistryOption("BackupCacheFiles")) == true)
                        m_logMessage = "The following " + totalToDelete.ToString() + " files would be deleted and backed up from the video thumbnail cache:\r\n\r\n" + m_logMessage;
                    else
                        m_logMessage = "The following " + totalToDelete.ToString() + " files would be deleted from the video thumbnail cache:\r\n\r\n" + m_logMessage;
                }
                else
                {
                    if (Convert.ToBoolean(RegistryHelper.LoadRegistryOption("BackupCacheFiles")) == true)
                        m_logMessage = "The following " + totalToDelete.ToString() + " files have been deleted and backed up from the video thumbnail cache:\r\n\r\n" + m_logMessage;
                    else
                        m_logMessage = "The following " + totalToDelete.ToString() + " files have been deleted from the video thumbnail cache:\r\n\r\n" + m_logMessage;
                }

                EventLogHelper.WriteEvent(m_logMessage, EventLogEntryType.Information);
            }
        }

        #endregion

        #region Video - New File Detect and Add to DB

        private void DetectNewMovies()
        {
            // If a video file exists and .TBN and .NFO file and optionally *-fanart.*
            
            // Create a hash
            // Copy the .TBN to the cache path and the -fanart to the cache
            // Insert into files, path, movie...
        }

        #endregion

        #region Music - Cleaning

        private void CleanMusicDatabase()
        {
            // Load the database records
            DatabaseHelper data = new DatabaseHelper(false);
            string sql = data.LoadSQLStatement("SQL.MusicSelectFiles.sql");
            List<DBObjectPath> dbFiles = data.Read<DBObjectPath>(sql);

            string logMessage = string.Empty;

            foreach (DBObjectPath dbFile in dbFiles)
            {
                if (dbFile.PhysicalFolder != null)
                {
                    if (!File.Exists(dbFile.PhysicalFile))
                    {
                        logMessage += "FileID=" + dbFile.FileID.ToString() + ", " + dbFile.PhysicalFile + "\r\n";

                        if (!m_LogOnly) DeleteMusicFromDatabase(dbFile);
                    }
                }
            }

            if (!m_LogOnly) PurgeMusicLinkTables();

            if (logMessage != string.Empty)
            {
                if (m_LogOnly)
                    logMessage = "The following files would be deleted from the database:\r\n\r\n" + logMessage;
                else
                    logMessage = "The following files have been deleted from the database:\r\n\r\n" + logMessage;

                EventLogHelper.WriteEvent(logMessage, EventLogEntryType.Information);
            }
        }

        private void DeleteMusicFromDatabase(DBObjectPath dbFile)
        {
            DatabaseHelper data = new DatabaseHelper(false);
            string sql = data.LoadSQLStatement("SQL.MusicDeleteFiles.sql");
            sql = String.Format(sql, dbFile.FileID);

            data.Execute(sql);
        }

        private void PurgeMusicLinkTables()
        {
            DatabaseHelper data = new DatabaseHelper(false);
            string sql = data.LoadSQLStatement("SQL.MusicPurgeLinkedTables.sql");
            data.Execute(sql);
        }

        private void CleanMusicThumbnailCache()
        {
            // Load the database records
            DatabaseHelper data = new DatabaseHelper(false);
            string sql = data.LoadSQLStatement("SQL.MusicSelectThumbnailCache.sql");
            List<DBCacheFile> databaseFiles = data.Read<DBCacheFile>(sql);

            string root = Path.Combine(RegistryHelper.LoadRegistryOption("LocalCacheFolder"), "Music");
            m_logMessage = string.Empty;
            int totalToDelete = 0;

            string[] fileList = GetFiles(root,
                new string[] { ".tbn", ".jpg", ".dds" },
                new string[] { @"*\LastFM\*" }, SearchOption.AllDirectories);

            foreach (string file in fileList)
            {
                if (!databaseFiles.Exists(f => f.CacheHashFile == Path.GetFileNameWithoutExtension(file)))
                {
                    totalToDelete++;

                    if (!m_LogOnly)
                        DeleteCacheFile(file);
                    else
                        m_logMessage += file + "\r\n";
                }
            }

            if (m_logMessage != string.Empty)
            {
                if (m_LogOnly)
                {
                    if (Convert.ToBoolean(RegistryHelper.LoadRegistryOption("BackupCacheFiles")) == true)
                        m_logMessage = "The following " + totalToDelete.ToString() + " files would be deleted and backed up from the music thumbnail cache:\r\n\r\n" + m_logMessage;
                    else
                        m_logMessage = "The following " + totalToDelete.ToString() + " files would be deleted from the music thumbnail cache:\r\n\r\n" + m_logMessage;
                }
                else
                {
                    if (Convert.ToBoolean(RegistryHelper.LoadRegistryOption("BackupCacheFiles")) == true)
                        m_logMessage = "The following " + totalToDelete.ToString() + " files have been deleted and backed up from the music thumbnail cache:\r\n\r\n" + m_logMessage;
                    else
                        m_logMessage = "The following " + totalToDelete.ToString() + " files have been deleted from the music thumbnail cache:\r\n\r\n" + m_logMessage;
                }

                EventLogHelper.WriteEvent(m_logMessage, EventLogEntryType.Information);
            }
        }

        #endregion

        #region Music - New File Detect and Add to DB
        #endregion

        #region General Events and Methods

        private void DeleteCacheFile(string file)
        {
            try
            {
                string rootFolder = RegistryHelper.LoadRegistryOption("LocalCacheFolder");
                string backupCacheFolder = RegistryHelper.LoadRegistryOption("BackupFolder");

                string relativeFile = file.Substring(rootFolder.Length);

                string sourcePathFile = (rootFolder + relativeFile);
                string backupPathFile = (backupCacheFolder + relativeFile);
                string backupFolder = new FileInfo(backupPathFile).DirectoryName;

                if (Convert.ToBoolean(RegistryHelper.LoadRegistryOption("BackupCacheFiles")) == true)
                {
                    if (!Directory.Exists(backupFolder)) Directory.CreateDirectory(backupFolder);

                    backupPathFile = MakeUnique(backupPathFile);
                    File.Move(sourcePathFile, backupPathFile);
                    
                    m_logMessage += file + " -> " + backupPathFile + "\r\n";
                }
                else
                {
                    File.Delete(sourcePathFile);

                    m_logMessage += file + "\r\n";
                }
            }
            catch (Exception ex)
            {
                EventLogHelper.WriteEvent("Error deleting cache file\r\n\r\n" + ex.ToString(), EventLogEntryType.Error);
            }
        }

        public string MakeUnique(string path)
        {
            string dir = Path.GetDirectoryName(path);
            string fileName = Path.GetFileNameWithoutExtension(path);
            string fileExt = Path.GetExtension(path);

            for (int i = 1; i < 50; i++)
            {
                if (!File.Exists(path))
                    return path;

                path = Path.Combine(dir, fileName + " (" + i + ")" + fileExt);
            }

            throw new Exception("Unable to find a unique filename to backup the cache file");
        }
        
        private string[] GetFiles(string sourceFolder, string[] exts, string[] exclude, System.IO.SearchOption searchOption)
        {
            try
            {
                string[] files = Directory.EnumerateFiles(sourceFolder, "*.*", searchOption)
                    .Where(f => exts.Contains(Path.GetExtension(f), StringComparer.OrdinalIgnoreCase)).ToArray();

                if (exclude != null)
                {
                    IEnumerable<string> rxfilters = from filter in exclude select string.Format("^{0}$", filter.Replace(@"\", @"\\").Replace(".", @"\.").Replace("*", ".*").Replace("?", "."));
                    Regex regex = new Regex(string.Join("|", rxfilters.ToArray()));

                    return files.Where(f => !regex.Match(f).Success).ToArray();
                }
                else
                    return files;
            }
            catch (Exception ex)
            {
                EventLogHelper.WriteEvent(String.Format("Error getting file list for {0}\r\n\r\n", sourceFolder) + ex.ToString(), EventLogEntryType.Error);
                return null;
            }
        }

        public bool StartTimer()
        {
            DatabaseHelper data;
            string sql = String.Empty;

            if (Convert.ToBoolean(RegistryHelper.LoadRegistryOption("CleanVideoDB")) == true)
            {
                // Check for a supported DB version before doing anything
                // Video DB version 42 (Dharma)
                // Video DB version 60 (Eden)
                // Video DB version 64 (Frodo)
                sql = "SELECT idVersion FROM version";
                data = new DatabaseHelper(true);
                List<DBVersion> databaseVersion = data.Read<DBVersion>(sql);

                if (databaseVersion[0].Version != 42 && databaseVersion[0].Version != 60)
                {
                    EventLogHelper.WriteEvent(String.Format("Unsupported MySQL video database version. Your version={0}, supported versions=Dharma (42) or Eden (60)", databaseVersion[0].Version.ToString()), EventLogEntryType.Error);
                    return false;
                }
            }
            else
            {
                if (Convert.ToBoolean(RegistryHelper.LoadRegistryOption("CleanMusicDB")) == true)
                {
                    // Check for a supported DB version before doing anything
                    // Music DB version 15 (Dharma)
                    // Music DB version 18 (Eden)
                    // Music DB version 20 (Frodo)
                    sql = "SELECT idVersion FROM version";
                    data = new DatabaseHelper(false);
                    List<DBVersion> databaseVersion = data.Read<DBVersion>(sql);

                    if (databaseVersion[0].Version != 15 && databaseVersion[0].Version != 18)
                    {
                        EventLogHelper.WriteEvent(String.Format("Unsupported MySQL music database version. Your version={0}, supported versions=Dharma (15) or Eden (18)", databaseVersion[0].Version.ToString()), EventLogEntryType.Error);
                        return false;
                    }
                }
            }

            m_LogOnly = Convert.ToBoolean(RegistryHelper.LoadRegistryOption("LogOnly"));

            // Load any Stored Procedures into the database
            if (Convert.ToBoolean(RegistryHelper.LoadRegistryOption("CleanMusicDB")) == true)
            {
                data = new DatabaseHelper(false);
                sql = data.LoadSQLStatement("SQL.MusicCreateSPs.sql");
                data.Execute(sql);
            }

            // Wait 10 seconds, then run every 30 minutes
            m_Timer.Change(TimeSpan.Parse("00:00:10"), TimeSpan.Parse("00:30:00"));

            return true;
        }

        private void EventTimerCallback(object state)
        {
            if (Convert.ToBoolean(RegistryHelper.LoadRegistryOption("CleanVideoDB")) == true)
                CleanVideoDatabase();

            if (Convert.ToBoolean(RegistryHelper.LoadRegistryOption("CleanVideoCache")) == true)
                CleanVideoThumbnailCache();

            if (Convert.ToBoolean(RegistryHelper.LoadRegistryOption("CleanMusicDB")) == true)
                CleanMusicDatabase();

            if (Convert.ToBoolean(RegistryHelper.LoadRegistryOption("CleanMusicCache")) == true)
                CleanMusicThumbnailCache();
        }

        #endregion
    }
}
